package repos

import common.{LoggerComponent, SerializationUtil}
import models.BaseModel
import play.api.db.slick.HasDatabaseConfig
import repos.Tables.BaseTable
import slick.driver.JdbcProfile
import slick.lifted.{CanBeQueryCondition, Rep, TableQuery}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag

abstract class BaseRepository[T <: BaseModel, F <: BaseTable[T]](tableQuery: TableQuery[F], cache: Pool)
  extends BaseRepositoryComponentQ[T, F]
    with BaseRepositoryComponent[T, F]
    with LoggerComponent {

  import profile.api._
  import slick.lifted.Rep


  val q: profile.api.TableQuery[F] = tableQuery

  def all = db.run(qq.result)

  def realRemove(id: Int)(implicit abc: ClassTag[T], executionContext: ExecutionContext): Future[Int] = db.run(realRemoveByIdQ(id))

  def save(t: T): Future[T] = {
    val opts = (for {
      id <- saveQ(t)
      o <- getByIdQ(id)
    } yield o.get).transactionally
    val run = db.run(opts)
    cache.withJedisClient(c => c.expire((t.getClass.getName + ":" + t.id).getBytes, 0))
    run
  }

  def update(t: T): Future[Int] = {
    val run = db.run(updateQ(t))
    cache.withJedisClient(c => c.expire((t.getClass.getName + ":" + t.id).getBytes(), 0))
    run
  }

  def remove(id: Int)(implicit abc: ClassTag[T]): Future[Int] = {
    val run = db.run(removeByIdQ(id))
    cache.withJedisClient(c => c.expire((abc.runtimeClass.getName + ":" + id).getBytes(), 0))
    run
  }

  def getById(id: Int)(implicit abc: ClassTag[T]): Future[Option[T]] = {
    val m = Future {
      cache.withJedisClient { c =>
        println("=================== abc.runtimeClass.getName = " + abc.runtimeClass.getName)
        Option(c.get((abc.runtimeClass.getName + ":" + id).getBytes)).map { model =>
          SerializationUtil.deserialize(model)
        }
      }
    }

    for {
      om <- m
      run <- om match {
        case Some(t) => Future.successful(om)
        case None =>
          db.run(getByIdQ(id)).map { case Some(f) =>
            cache.withJedisClient(c => c.set((abc.runtimeClass.getName).getBytes, SerializationUtil.serialize(f)))
            Some(f)
          }
      }
    } yield run
  }

  def filter[C <: Rep[_]](expr: F => C, page: Int = 1, limit: Option[Int] = None)(implicit wt: CanBeQueryCondition[C]): Future[(TotalCount, Seq[T])] = {
    val fr = qq.filter(expr)
    val cj = limit
      .map(limit => {
        val paged = fr.drop((page - 1) * limit).take(limit)
        paged
      })
      .getOrElse(fr)
    val total = db.run(fr.size.result)
    val result = db.run(cj.result)
    for {
      t <- total
      r <- result
    } yield (t, r)
  }
}

trait BaseRepositoryComponent[T <: BaseModel, F <: BaseTable[T]] {

  type TotalCount = Int

  def save(t: T): Future[T]

  def filter[C <: Rep[_]](expr: (F) => C, page: Int = 1, limit: Option[Int] = None)(implicit wt: CanBeQueryCondition[C]): Future[(TotalCount, Seq[T])]

  def update(t: T): Future[Int]

  def remove(id: Int)(implicit abc: ClassTag[T]): Future[Int]

  def getById(id: Int)(implicit abc: ClassTag[T]): Future[Option[T]]

  def realRemove(id: Int)(implicit abc: ClassTag[T], executionContext: ExecutionContext): Future[Int]

  def all: Future[Seq[T]]
}

trait BaseRepositoryComponentQ[T <: BaseModel, F <: BaseTable[T]] extends HasDatabaseConfig[JdbcProfile] {

  import profile.api._

  val q: profile.api.type#TableQuery[F]

  def qq = q.filter(_.isDeleted === false)

  def removeByIdQ(id: Int) = {
    qq.filter(_.id === id).map(_.isDeleted).update(true)
  }

  def updateQ(t: T) = {
    qq.filter(_.id === t.id).update(t)
  }

  def saveQ(t: T) = {
    (q returning q.map(_.id)) += t
  }


  def getByIdQ(id: Int) = qq.filter(_.id === id).result.headOption

  def realRemoveByIdQ(id: Int) = qq.filter(_.id === id).delete

  def savesQ(seqT: Seq[T]) = {
    (q returning q.map(_.id)) ++= seqT
  }
}
